package cn.wmx.sqlsession;

import cn.wmx.pojo.Configuration;
import cn.wmx.pojo.MappedStatement;

import java.lang.reflect.*;
import java.util.List;
import java.util.Map;

public class DefaultSqlSession implements SqlSession {

    private Configuration configuration;

    public DefaultSqlSession(Configuration configuration) {
        this.configuration = configuration;
    }

    @Override
    public <E> List<E> selectList(String statementId, Object... objects) throws Exception {
        SimpleExecutor simpleExecutor = new SimpleExecutor();
        MappedStatement mappedStatement = configuration.getMappedStatementMap().get(statementId);
        List<Object> list = simpleExecutor.query(configuration, mappedStatement, objects);
        return (List<E>) list;
    }

    @Override
    public <T> T selectOne(String statementId, Object... objects) throws Exception {
        List<Object> list = this.selectList(statementId, objects);
        if (list == null || list.size() == 0) return null;
        if (list.size() == 1) return (T) list.get(0);
        throw new RuntimeException("返回结果集过多");
    }

    @Override
    public int executeUpdate(String statementId, Object... objects) throws Exception {
        SimpleExecutor simpleExecutor = new SimpleExecutor();
        MappedStatement mappedStatement = configuration.getMappedStatementMap().get(statementId);
        return simpleExecutor.executeUpdate(configuration, mappedStatement, objects);
    }

    @Override
    public <T> T getMapper(Class<?> mapperClass) throws Exception {
        // 使用JDK动态代理，为Dao层接口实现代理对象，执行并返回结果
        /**
         * 1.类加载器
         * 2.Class 数组
         * 3.动态代理必须要实现的接口
         */
        Object proxyInstance = Proxy.newProxyInstance(
                DefaultSqlSession.class.getClassLoader(),
                new Class[]{mapperClass},
                new InvocationHandler() {
                    /**
                     * 参数介绍
                     * @param proxy 当前代理对象的引用
                     * @param method 当前被调用方法的引用，例如，userDao.findAll(),findAll() 就是 method
                     * @param args 传递的参数
                     * @return
                     * @throws Throwable
                     */
                    @Override
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        // 代理方法仍是要执行 selectList、selectOne 或是其他方法，所以需要准备这些方法所需的参数
                        // 代理方法中取不到以 namespace + 标签的 Id 的方式组成的 statementId，所以有以下约定
                        // 1. mapper.xml <mapper> 标签的 namespace 与接口的全路径名相同，即：cn.wmx.dao.IUserDao（接口全限定名）
                        // 2. mapper.xml 子标签的 id 必须与接口中方法名保持一致，

                        // 方法名
                        String methodName = method.getName();
                        // 接口全限定名
                        String className = method.getDeclaringClass().getName();
                        // 参数 2 params = args
                        // 参数 1
                        String statementId = className + "." + methodName;

                        Map<String, MappedStatement> mappedStatementMap = configuration.getMappedStatementMap();
                        String tagName = mappedStatementMap.get(statementId).getTagName();
                        switch (tagName) {
                            case "insert":
                            case "update":
                            case "delete":
                                return executeUpdate(statementId, args);
                            case "select":
                                // 获取当前被调用方法的返回值类型
                                Type genericReturnType = method.getGenericReturnType();
                                // 是否进行了泛型类型参数化
                                if (genericReturnType instanceof ParameterizedType) {
                                    return selectList(statementId, args);
                                }
                                return selectOne(statementId, args);
                            default:
                                throw new RuntimeException("暂不支持【" + tagName + "】");
                        }
                    }
                }
        );
        return (T) proxyInstance;
    }

}
